package com.lmk.ms.common.mvc.service.impl;

import cn.hutool.core.collection.CollUtil;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.json.JsonData;
import com.lmk.ms.common.constants.StatusEnum;
import com.lmk.ms.common.dto.db.PageResult;
import com.lmk.ms.common.dto.db.QueryParams;
import com.lmk.ms.common.dto.db.Search;
import com.lmk.ms.common.dto.db.Sort;
import com.lmk.ms.common.dto.es.EsFieldSearchParameter;
import com.lmk.ms.common.dto.es.EsSearchParameter;
import com.lmk.ms.common.dto.es.EsSearchResult;
import com.lmk.ms.common.dto.mvc.Meta;
import com.lmk.ms.common.dto.mvc.ServiceResult;
import com.lmk.ms.common.entity.PkEntity;
import com.lmk.ms.common.mvc.service.EsBaseService;
import com.lmk.ms.common.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j
public abstract class EsBaseServiceImpl<D extends ElasticsearchRepository<T, PK>, T extends PkEntity<PK>, PK extends Serializable> implements EsBaseService<T, PK> {
    @Autowired
    protected D dao;

    /** 实体类的类型 */
    protected Class<T> entityClass = currentEntityClass();

    @Autowired
    protected ElasticsearchOperations operations;

    public D getDao() {
        return dao;
    }

    /** 获取实体类的类型 */
    protected Class<T> currentEntityClass() {
        return BeanUtils.getSuperClassGenerics(this.getClass(), 1);
    }

    @Override
    public Meta loadMetaInfo() {
        return new Meta();
    }

    @Override
    public T getById(PK id) {
        Optional<T> result = dao.findById(id);
        return result.orElse(null);
    }

    @Override
    public PageResult<T> list(QueryParams queryParams, List<Search> searchList, List<Sort> sortList) {
        // 条件
        BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
        if (CollUtil.isNotEmpty(searchList)) {
            for (Search search : searchList) {
                switch (search.getOperator()) {
                    case match:
                        boolQueryBuilder.must(MatchQuery.of(t -> t.field(search.getName()).query((String) search.getValue()))._toQuery());
                        break;
                    case like:
                        boolQueryBuilder.must(WildcardQuery.of(t -> t.field(search.getName()).value("*" + search.getValue() + "*"))._toQuery());
                        break;
                    case startWith:
                        boolQueryBuilder.must(PrefixQuery.of(t -> t.field(search.getName()).value((String) search.getValue()))._toQuery());
                        break;
                    case eq:
                        boolQueryBuilder.must(TermQuery.of(t -> t.field(search.getName()).value((String) search.getValue()))._toQuery());
                        break;
                    case in:
                        List<FieldValue> values = search.getValues().stream().map(v -> FieldValue.of((String) v)).collect(Collectors.toList());
                        boolQueryBuilder.must(TermsQuery.of(t -> t.field(search.getName()).terms(
                                new TermsQueryField.Builder().value(values).build())
                        )._toQuery());
                        break;
                    case between:
                        boolQueryBuilder.must(RangeQuery.of(t -> t.field(search.getName()).gte(JsonData.of(search.getStart())).lte(JsonData.of(search.getEnd())))._toQuery());
                }
            }
        }

        NativeQueryBuilder nativeQueryBuilder = NativeQuery.builder().withQuery(boolQueryBuilder.build()._toQuery());

        // 排序
        if(CollUtil.isNotEmpty(sortList)){
            for (Sort sort : sortList){
                nativeQueryBuilder.withSort(
                    org.springframework.data.domain.Sort.by("ASC".equalsIgnoreCase(sort.getType()) ? org.springframework.data.domain.Sort.Direction.ASC : org.springframework.data.domain.Sort.Direction.DESC, sort.getColumn())
                );
            }
        }

        // 分页
        Integer pageNumber = queryParams.getPage();
        Integer pageSize = queryParams.getSize();
        nativeQueryBuilder.withPageable(PageRequest.of(pageNumber- 1, pageSize));

        // 消除10000条结果限制
        nativeQueryBuilder.withTrackTotalHits(true);

        // 构建查询
        NativeQuery nativeQuery = nativeQueryBuilder.build();

        // 执行查询
        SearchHits<T> searchHits = operations.search(nativeQuery, entityClass);

        List<T> list = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
        return new PageResult<>(pageNumber, pageSize, searchHits.getTotalHits(), list);
    }

    @Override
    public EsSearchResult<T> search(EsSearchParameter parameter) {
        List<EsFieldSearchParameter> searchList = parameter.getSearchList();
        List<Sort> sortList = parameter.getSortList();
        Integer pageNumber = parameter.getPage();
        Integer pageSize = parameter.getSize();

        BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool(); // 查询条件
        List<HighlightField> highlightFieldList = new ArrayList<>(); // 高亮显示
        HighlightFieldParameters highlightFieldParameters = HighlightFieldParameters.builder().withPreTags("<em>").withPostTags("</em>").build();
        if (CollUtil.isNotEmpty(searchList)) {
            for (EsFieldSearchParameter fieldSearch : searchList) {
                // 封装查询条件
                switch (fieldSearch.getSearchType()){
                    case term -> {
                        switch (fieldSearch.getDataType()){
                            case text -> boolQueryBuilder.must(TermQuery.of(t -> t.field(fieldSearch.getName()).value(fieldSearch.getValue().to(String.class)))._toQuery());
                            case integer -> boolQueryBuilder.must(TermQuery.of(t -> t.field(fieldSearch.getName()).value(fieldSearch.getValue().to(Integer.class)))._toQuery());
                            case bigInteger -> boolQueryBuilder.must(TermQuery.of(t -> t.field(fieldSearch.getName()).value(fieldSearch.getValue().to(Long.class)))._toQuery());
                            case decimal -> boolQueryBuilder.must(TermQuery.of(t -> t.field(fieldSearch.getName()).value(fieldSearch.getValue().to(Double.class)))._toQuery());
                            case bool -> boolQueryBuilder.must(TermQuery.of(t -> t.field(fieldSearch.getName()).value(fieldSearch.getValue().to(Boolean.class)))._toQuery());
                        }
                    }
                    case terms ->{
                        if(CollUtil.isNotEmpty(fieldSearch.getValues())){
                            List<FieldValue> values = fieldSearch.getValues().stream().map(FieldValue::of).collect(Collectors.toList());
                            boolQueryBuilder.must(
                                TermsQuery.of(
                                    t -> t.field(fieldSearch.getName()).terms(new TermsQueryField.Builder().value(values).build())
                                )._toQuery()
                            );
                        }
                    }
                    case range -> {
                        JsonData from = fieldSearch.getFrom();
                        JsonData to = fieldSearch.getTo();
                        if(from != null && to != null){
                            boolQueryBuilder.must(RangeQuery.of(t -> t.field(fieldSearch.getName()).gte(from).lte(to))._toQuery());
                        } else if (to == null) {
                            boolQueryBuilder.must(RangeQuery.of(t -> t.field(fieldSearch.getName()).gte(from))._toQuery());
                        } else {
                            boolQueryBuilder.must(RangeQuery.of(t -> t.field(fieldSearch.getName()).lte(to))._toQuery());
                        }
                    }
                    case match -> boolQueryBuilder.must(MatchQuery.of(t -> t.field(fieldSearch.getName()).query(fieldSearch.getValue().to(String.class)))._toQuery());
                    case matchPhrase -> boolQueryBuilder.must(MatchPhraseQuery.of(t -> t.field(fieldSearch.getName()).query(fieldSearch.getValue().to(String.class)))._toQuery());
                    case startWith -> boolQueryBuilder.must(PrefixQuery.of(t -> t.field(fieldSearch.getName()).value(fieldSearch.getValue().to(String.class)))._toQuery());
                }

                // 封装高亮显示
                if(fieldSearch.getHighlight()){
                    highlightFieldList.add(new HighlightField(fieldSearch.getName(), highlightFieldParameters));
                }
            }
        }

        NativeQueryBuilder nativeQueryBuilder = NativeQuery.builder().withQuery(boolQueryBuilder.build()._toQuery());



        // 高亮
        if(CollUtil.isNotEmpty(highlightFieldList)){
            Highlight highlight = new Highlight(highlightFieldList);
            HighlightQuery highlightQuery = new HighlightQuery(highlight, entityClass);
            nativeQueryBuilder.withHighlightQuery(highlightQuery);
        }


        // 排序
        if(CollUtil.isNotEmpty(sortList)){
            for (Sort sort : sortList){
                nativeQueryBuilder.withSort(
                        org.springframework.data.domain.Sort.by("ASC".equalsIgnoreCase(sort.getType()) ? org.springframework.data.domain.Sort.Direction.ASC : org.springframework.data.domain.Sort.Direction.DESC, sort.getColumn())
                );
            }
        }

        // 分页
        nativeQueryBuilder.withPageable(PageRequest.of(pageNumber- 1, pageSize));

        // 消除10000条结果限制
        nativeQueryBuilder.withTrackTotalHits(true);

        // 构建查询
        NativeQuery nativeQuery = nativeQueryBuilder.build();

        // 执行查询
        SearchHits<T> searchHits = operations.search(nativeQuery, entityClass);

        EsSearchResult<T> result = new EsSearchResult<>();

        //处理搜索结果
        List<T> records = new ArrayList<>();
        Set<String> highlightFields = null;

        Map<String, Map<String, String>> highlights = new LinkedHashMap<>();
        Map<String, String> fieldHighlights = null;

        List<SearchHit<T>> searchHitsList = searchHits.getSearchHits();

        for(SearchHit<T> hits : searchHitsList){
            records.add(hits.getContent());
            highlightFields = hits.getHighlightFields().keySet();
            if(CollUtil.isNotEmpty(highlightFields)){
                fieldHighlights = new LinkedHashMap<>();
                for(String hf : highlightFields){
                    fieldHighlights.put(hf, hits.getHighlightField(hf).get(0));
                }
                highlights.put(hits.getId(), fieldHighlights);
            }
        }

        result.setHighlights(highlights);
        result.setData(new PageResult<>(pageNumber, pageSize, searchHits.getTotalHits(), records));
        return result;
    }

    @Override
    public ServiceResult create(T entity) {
        dao.save(entity);
        return new ServiceResult(StatusEnum.SUCCESS);
    }

    @Override
    public ServiceResult update(T entity) {
        dao.save(entity);
        return new ServiceResult(StatusEnum.SUCCESS);
    }

    @Override
    public ServiceResult deleteById(PK id) {
        dao.deleteById(id);
        return new ServiceResult(StatusEnum.SUCCESS);
    }

    @Override
    public ServiceResult deleteByIds(Collection<PK> ids) {
        ids.forEach(this::deleteById);
        return new ServiceResult(StatusEnum.SUCCESS);
    }
}